/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.reserve;

import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedList;

import jp.aist.gtrc.plus.scheduler.node.NodeInfo;
import jp.aist.gtrc.plus.scheduler.node.NodeManager;
import static jp.aist.gtrc.plus.reserve.ReserveStatus.State.*;


public class ReserveInfo extends ReserveStatus {

	private static final long serialVersionUID = 2080115344425833607L;

	private enum NodeAllocateState {
		Allocated, Reallocating, Freed
	}

	private ReserveRequest prevReq = null;
	private ReserveNodeSet rsvNodeSet = null;
	private ReserveNodeSet prevNodeSet = null;
	private NodeAllocateState allocState;

	public ReserveInfo(ReserveId id, ReserveRequest req, ReserveNodeSet rsvNodeSet) throws ReserveException{

		super(id, req, Confirmed);

		this.allocState = NodeAllocateState.Freed;
		registerReservedNodes(rsvNodeSet);

	}

	public final ReserveStatus getReserveStatus() {

		return (ReserveStatus)this;

	}

	void recoverTransient(NodeManager nodeMgr) {

		if (rsvNodeSet != null) {
			rsvNodeSet.recoverTransient(nodeMgr);
			// point 'this' from reserved nodes
			for (NodeInfo node : rsvNodeSet.getAll()) {
				node.addReserveInfo(this);
			}
		}
		if (prevNodeSet != null) {
			prevNodeSet.recoverTransient(nodeMgr);
		}

	}

	void setAsError() {

		// NOTE: this method is called when DB access is failed.
		setReserveState(Error);
		try {
			unregisterReservedNodesForever();
		} catch (ReserveException e) {
			// ignore to continue
		}

	}

	private void changeReserveNodeSet(ReserveNodeSet newNodeSet) {

		if (this.rsvNodeSet != null) {
			this.prevNodeSet = this.rsvNodeSet;
		}
		this.rsvNodeSet = newNodeSet;

	}

	private void recoverPreviousReserveNodeSet() throws ReserveException {

		if ((prevNodeSet != null) && (prevNodeSet.equals(rsvNodeSet) == false)) {
			registerReservedNodes(prevNodeSet);
			prevNodeSet = null;
		}

	}

	protected void registerReservedNodes(ReserveNodeSet rsvNodeSet) throws ReserveException {

		assert(rsvNodeSet != null);

		if (this.rsvNodeSet != null) {
			for (NodeInfo node : this.rsvNodeSet.getAll()) {
				node.removeReserveInfo(this);
			}
		}

		changeReserveNodeSet(rsvNodeSet);

		// point 'this' from reserved nodes
		for (NodeInfo node : rsvNodeSet.getAll()) {
			node.addReserveInfo(this);
		}

		setReservedNodes(rsvNodeSet.getNames());

		allocState = NodeAllocateState.Allocated;

	}

	protected void unregisterReservedNodesForever() throws ReserveException {

		if (rsvNodeSet != null) {
			setReservedNodes(new String[]{});
			for (NodeInfo node : rsvNodeSet.getAll()) {
				node.removeReserveInfo(this);
			}
			changeReserveNodeSet(null);
		}
		allocState = NodeAllocateState.Freed;

	}

	void unregisterReservedNodesTemporary() {

		if (allocState == NodeAllocateState.Allocated) {
			allocState = NodeAllocateState.Reallocating;
		} else {
			assert(false);
		}

	}

	void recoverReservedNodes() throws ReserveException {

		switch (allocState) {
		case Allocated:
		case Freed:
			recoverPreviousReserveNodeSet();
			break;
		case Reallocating:
			allocState = NodeAllocateState.Allocated;
			break;
		default:
			assert(false);
			break;
		}

	}

	protected void changeReserveRequest(ReserveRequest oldReq, ReserveRequest newReq) throws ReserveException {

		modifyRequest(newReq);

	}

	private void recoverReserveRequest() throws ReserveException {

		ReserveRequest curReq = getReserveRequest();
		if ((prevReq != null) && (prevReq.equals(curReq) == false)) {
			changeReserveRequest(curReq, prevReq);
			prevReq = null;
		}

	}

	void modify(ReserveRequest newReq) throws ReserveException {

		prevReq = new ReserveRequest(getReserveRequest());
		changeReserveRequest(prevReq, newReq);

	}

	boolean modify(ReserveNodeSet newNodeSet) throws ReserveException {

		if (newNodeSet == null) {
			unregisterReservedNodesForever();
			return true;
		}

		if (newNodeSet.equals(rsvNodeSet)) {
			if (allocState == NodeAllocateState.Reallocating) {
				recoverReservedNodes();
			}
			return false;		// not changed
		}

		registerReservedNodes(newNodeSet);
		setReserveState(Confirmed);
		return true;			// changed

	}

	boolean modify(ReserveRequest req, ReserveNodeSet newNodeSet) throws ReserveException {

		modify(req);
		return modify(newNodeSet);

	}

	void recoverModification() throws ReserveException {

		recoverReserveRequest();
		recoverReservedNodes();

	}

	void commitModification() {

		prevReq = null;
		prevNodeSet = null;

	}

	public boolean isOverlap(Calendar start, Calendar end){

		if (allocState == NodeAllocateState.Allocated) {
			Calendar rsvStart = getStartTime();
			Calendar rsvEnd = getEndTime();
			return (rsvStart.before(end) && start.before(rsvEnd));
		} else {
			return false;
		}

	}

	public Collection<NodeInfo> getReservedNodeInfos(){

		if ((allocState == NodeAllocateState.Allocated) && (rsvNodeSet != null)) {
			return rsvNodeSet.getAll();
		} else {
			return new LinkedList<NodeInfo>();
		}

	}

	public ReserveNodeSet getReserveNodeSet() {

		return (allocState == NodeAllocateState.Allocated) ? rsvNodeSet : null;

	}

	public boolean isExpired(Calendar now) {

		if (now == null) {
			now = Calendar.getInstance();
		}
		// !(now < end) means (now >= end), including equal
		return (now.before(getEndTime()) == false);

	}

	void resetReservedNodeNames() {

		rsvNodeSet.resetNodeNames();
		setReservedNodes(rsvNodeSet.getNames());

	}

	public String toString() {

		String s = super.toString();
		s += "allocState: " + allocState;
		if (rsvNodeSet != null) {
			s += "\nrsvNodeSet: " + rsvNodeSet.toString();
		}
		if (prevReq != null) {
			s += "\nprevReq: " + prevReq.toString();
		}
		if (prevNodeSet != null) {
			s += "\nprevNodeSet: " + prevNodeSet.toString();
		}

		return s + "\n";

	}

}
